/**
 * 
 */
package com.ditty.handler;

import java.awt.List;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;

import com.ditty.HttpConfiguration;
import com.ditty.bean.ResponseBody;
import com.ditty.controller.ControllerFactory;
import com.ditty.invoke.action.Action;
import com.ditty.invoke.action.ActionInvoker;
import com.ditty.invoke.handler.HandlerInvoker;
import com.ditty.kit.ArrayKit;
import com.ditty.kit.CharsetKit;
import com.ditty.kit.HttpKit;
import com.ditty.kit.StrKit;
import com.ditty.kit.ValueKit;
import com.ditty.router.Routers;
import com.ditty.serialize.ISerializeConvertor;
import com.ditty.serialize.SerializeConvertorFactory;
import com.ditty.wrap.HttpRequestWrapper;
import com.ditty.wrap.HttpResponseWrapper;

/**
 * @author dingnate
 *
 */
public class ActionHandler extends AbstractHttpHandler {

	@Override
	public void handle(HandlerInvoker invoker) {
		String[] urlTails = { null };
		Action action = Routers.me().get(invoker.getHttpRequestWrapper().method(), invoker.getTarget(), urlTails);
		if (action == null) {
			invoker.getHttpResponseWrapper().setStatusCode(HttpKit.STATUS_404);
			return;
		}
		try {
			handleReturnValue(action.getMethod().getReturnType() == void.class, invoker.getHttpResponseWrapper(), new ActionInvoker(action,
					ControllerFactory.getController(action.getControllerClass()), buildArgs(action, urlTails[0], invoker.getHttpRequestWrapper()))
					.inovke().getReturnValue());
		} catch (Exception e) {
			throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
		}
	}

	private void handleReturnValue(boolean isVoid, HttpResponseWrapper httpResponseWrapper, Object returnValue) throws IOException {
		if (!isVoid) {
			httpResponseWrapper.write(SerializeConvertorFactory.getSerializeConvertor().serialize(new ResponseBody(returnValue)));
		}
	}

	@SuppressWarnings({ "unchecked" })
	private Object[] buildArgs(Action action, String urlTail, HttpRequestWrapper httpRequestWrapper) throws UnsupportedEncodingException {
		Class<?>[] argTypes = action.getMethod().getParameterTypes();
		Object[] objects = new Object[argTypes.length];
		ISerializeConvertor serializeConvertor = SerializeConvertorFactory.getSerializeConvertor();
		if (StrKit.isNotBlank(urlTail)) {
			if (urlTail.charAt(0) == ValueKit.CHAR_SLASH) {
				//url param
				String[] split = StrKit.split(urlTail.substring(1), HttpConfiguration.ARG_SEPARATOR, argTypes.length);
				if (split.length != argTypes.length) {
					throw new RuntimeException(String.format("arg count is unmatch! expect %s get %s", argTypes.length, split.length));
				}
				for (int i = 0; i < argTypes.length; i++) {
					objects[i] = buildArg(serializeConvertor, URLDecoder.decode(split[i], CharsetKit.STR_DEFAULT_CHARSET), argTypes[i]);
				}
			} else if (urlTail.charAt(0) == ValueKit.CHAR_QUERY) {
				//query param
				String[] expressions = StrKit.split(urlTail.substring(1), ValueKit.STR_OP_AND);
				if (expressions.length != argTypes.length) {
					throw new RuntimeException(String.format("arg count is unmatch! expect %s get %s", argTypes.length, expressions.length));
				}
				String[] argNames = action.getArgNames();
				for (String expression : expressions) {
					String[] entry = StrKit.split(expression, ValueKit.STR_OP_EQUAL, 2);
					String argName = entry[0];
					String value = URLDecoder.decode(entry[1], CharsetKit.STR_DEFAULT_CHARSET);
					int argIndex;
					if (Character.isDigit(argName.charAt(0))) {
						argIndex = Integer.valueOf(argName);
					} else {
						argIndex = ArrayKit.indexOf(argNames, argName);
					}
					objects[argIndex] = buildArg(serializeConvertor, value, argTypes[argIndex]);
				}
			}
		} else {
			//request body
			HashMap<String, Object> argMap = serializeConvertor.deserialize(httpRequestWrapper.content(), HashMap.class);
			if (argMap.size() != argTypes.length) {
				throw new RuntimeException(String.format("arg count is unmatch! expect %s get %s", argTypes.length, argMap.size()));
			}
			String[] argNames = action.getArgNames();
			for (Entry<String, Object> entry : argMap.entrySet()) {
				// build arg object
				String argName = entry.getKey();
				String value = entry.getValue().toString();
				int argIndex;
				if (Character.isDigit(argName.charAt(0))) {
					argIndex = Integer.valueOf(argName);
				} else {
					argIndex = ArrayKit.indexOf(argNames, argName);
				}
				objects[argIndex] = buildArg(serializeConvertor, value, argTypes[argIndex]);
			}
		}
		return objects;
	}

	/**
	 * @param serializeConvertor
	 * @param value
	 * @param argType
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Object buildArg(ISerializeConvertor serializeConvertor, String value, Class<?> argType) {
		Object argObject;
		Class<?> componentType = argType.getComponentType();
		if (argType.isArray()) {
			argObject = serializeConvertor.deserializeArray(value.getBytes(CharsetKit.DEFAULT_CHARSET), componentType).toArray(
					(Object[]) Array.newInstance(componentType, 0));
		} else if (List.class.isAssignableFrom(argType)) {
			argObject = serializeConvertor.deserializeArray(value.getBytes(CharsetKit.DEFAULT_CHARSET), componentType);
		} else if (Set.class.isAssignableFrom(argType)) {
			argObject = new HashSet(serializeConvertor.deserializeArray(value.getBytes(CharsetKit.DEFAULT_CHARSET), componentType));
		} else {
			argObject = serializeConvertor.deserialize(value.getBytes(CharsetKit.DEFAULT_CHARSET), argType);
		}
		return argObject;
	}
}
